/*
 * Copyright (c) 2013, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 *  ======== Mmu.c ========
 */

#include <xdc/std.h>
#include <string.h>
#include <xdc/runtime/Assert.h>
#include <xdc/runtime/Startup.h>
#include <ti/sysbios/hal/Hwi.h>

#if defined(ti_targets_arm_elf_A8F) || \
    defined(ti_targets_arm_elf_A8Fnv) || \
    defined(gnu_targets_arm_A8F)

#include <ti/sysbios/family/arm/a8/Cache.h>

#elif defined(gnu_targets_arm_A9F)

#include <ti/sysbios/family/arm/a9/Cache.h>

#endif

#include "package/internal/Mmu.xdc.h"

/*
 *  ======== Mmu_Module_startup ========
 */
Int Mmu_Module_startup(Int phase)
{
    /* disable the MMU */
    Mmu_disable();

    /* zero out the descriptor table entries */
    memset(Mmu_module->tableBuf, 0, 0x4000);

    /*
     *  Initialize the descriptor table based upon static config
     *  This function is generated by the Mmu.xdt file.
     */
    Mmu_initTableBuf(Mmu_module->tableBuf);

    Mmu_init();

    if (Mmu_enableMMU) {
        /* enable the MMU */
        Mmu_enable();
    }

    return (Startup_DONE);
}

/*
 *  ======== Mmu_disable ========
 *  Function to disable the MMU.
 */
Void Mmu_disable()
{
    UInt16 type;
    UInt   key;

    /* if MMU is alreay disabled, just return */
    if (!(Mmu_isEnabled())) {
        return;
    }

    key = Hwi_disable();

    /* get the current enabled bits */
    type = Cache_getEnabled();

    /* disable all enabled caches */
    Cache_disable(type);

    /* disables the MMU */
    Mmu_disableAsm();

    /* set cache back to initial settings */
    Cache_enable(type);

    Hwi_restore(key);
}

/*
 *  ======== Mmu_enable ========
 *  Function to enable the MMU.
 */
Void Mmu_enable()
{
    UInt16 type;
    UInt   key;

    /* if MMU is already enabled then just return */
    if (Mmu_isEnabled()) {
        return;
    }

    key = Hwi_disable();

    /* get the current enabled bits */
    type = Cache_getEnabled();

    if (type & Cache_Type_ALLP) {
        /* invalidate all of L1 program cache */
        Cache_invL1pAll();

        /* disable L1P cache */
        Cache_disable(Cache_Type_ALLP);
    }

    /* enables the MMU */
    Mmu_enableAsm();

    /* set cache back to initial settings */
    Cache_enable(type);

    Hwi_restore(key);
}

/*
 *  ======== Mmu_initDescAttrs ========
 */
Void Mmu_initDescAttrs(Mmu_FirstLevelDescAttrs *attrs)
{
    /* Assert that attrs != NULL */
    Assert_isTrue(attrs != NULL, Mmu_A_nullPointer);

    attrs->type = Mmu_defaultAttrs.type;
    attrs->tex = Mmu_defaultAttrs.tex;
    attrs->bufferable = Mmu_defaultAttrs.bufferable;
    attrs->cacheable = Mmu_defaultAttrs.cacheable;
    attrs->shareable = Mmu_defaultAttrs.shareable;
    attrs->noexecute = Mmu_defaultAttrs.noexecute;
    attrs->domain = Mmu_defaultAttrs.domain;
    attrs->imp = Mmu_defaultAttrs.imp;
    attrs->accPerm = Mmu_defaultAttrs.accPerm;
}

/*
 *  ======== Mmu_setFirstLevelDesc ========
 */
Void Mmu_setFirstLevelDesc(Ptr virtualAddr, Ptr phyAddr,
                           Mmu_FirstLevelDescAttrs *attrs)
{
    UInt32 index = (UInt32)virtualAddr >> 20;
    UInt32 desc = 0;
    Bool   enabled;

    /* Assert that attrs != NULL */
    Assert_isTrue(attrs != NULL, Mmu_A_nullPointer);

    /* determine the current state of the MMU */
    enabled = Mmu_isEnabled();

    /* disable the MMU (if already disabled, does nothing) */
    Mmu_disable();

    /* Determine which kind of descriptor. */
    switch (attrs->type) {
        case Mmu_FirstLevelDesc_PAGE_TABLE:
            /* Page table descriptor */
            desc = (attrs->type) |
                   (attrs->domain << 5) |
                   (attrs->imp << 9) |
                   ((UInt32)phyAddr & 0xFFFFFC00);
            break;

        /* Section descriptor */
        case Mmu_FirstLevelDesc_SECTION:
            desc = (attrs->type) |
                   (attrs->bufferable << 2) |
                   (attrs->cacheable << 3) |
                   (attrs->noexecute << 4) |
                   (attrs->domain << 5) |
                   (attrs->imp << 9) |
                   ((attrs->accPerm & 0x3) << 10) |
                   ((attrs->tex & 0x7) << 12) |
                   ((attrs->accPerm & 0x4) << 13) |
                   (attrs->shareable << 16) |
                   ((UInt32)phyAddr & 0xFFF00000);
            break;

        default:
            Assert_isTrue(FALSE, Mmu_A_unknownDescType);
            break;
    }

    /* set the entry in the first level descriptor table */
    Mmu_module->tableBuf[index] = desc;

    if (enabled) {
        /* if MMU was enabled, then re-enable it */
        Mmu_enable();
    }
}

/*
 *  ======== Mmu_getPhysicalAddr ========
 *  Return translated physical address for given virtual address
 */

Ptr Mmu_getPhysicalAddr(Ptr virtualAddr)
{
    Ptr physicalAddr = Mmu_getPhysicalAddrI(virtualAddr);

    if ((UInt32)physicalAddr & 0x1) {
        return (Ptr)(~0);
    }

    physicalAddr = (Ptr)(((UInt32)physicalAddr & 0xFFFFF000) |
            ((UInt32)virtualAddr & 0xFFF));

    return physicalAddr;
}

/*
 *  ======== Mmu_getPhysicalAddrI ========
 */
#if defined(__GNUC__) && !defined(__ti__)
Ptr __attribute__((naked)) Mmu_getPhysicalAddrI(Ptr virtualAddr)
{
    __asm__ __volatile__ (
            "mcr p15, #0, r0, c7, c8, #0 \n\t" /* write virt addr to ATS1CPR */
            "isb \n\t"                         /* flush instruction pipeline */
            "mrc p15, #0, r0, c7, c4, #0 \n\t" /* read physical addr[31:12]
                                                  from PAR */
            "bx lr"
            );
}
#elif defined(__ti__)
    __asm("    .sect \".text:ti_sysbios_family_arm_a8_Mmu_getPhysicalAddrI__I\"\n"
          "    .clink\n"
          "    .global ti_sysbios_family_arm_a8_Mmu_getPhysicalAddrI__I\n"
          "    .armfunc ti_sysbios_family_arm_a8_Mmu_getPhysicalAddrI__I\n"
          "    .state32\n"
          "ti_sysbios_family_arm_a8_Mmu_getPhysicalAddrI__I:\n"
          "    mcr p15, #0, r0, c7, c8, #0 \n" /* write virt addr to ATS1CPR */
          "    isb \n"                         /* flush instruction pipeline */
          "    mrc p15, #0, r0, c7, c4, #0 \n" /* read physical addr[31:12]
                                                  from PAR */
          "    bx lr\n");
#endif
